Conversation
| ): StravaAuthenticationDto { | ||
| return api.getAccessToken( | ||
| clientId = StravaClientSecret.STRAVA_CLIENT_ID, | ||
| clientSecret = StravaClientSecret.STRAVA_CLIENT_SECRET, |
There was a problem hiding this comment.
These Constants are in a file in the repository that is not added to GitHub. It is because a client secret is very dangerous information to have in a public GitHub.
There are much better ways of doing this, and I'll try to improve it in the future. For now, in order to make the app work after this PR, you will need to make a file at com.majotyler.hiittimer.network.StravaClientSecret in commonMain and I will send you what you should put in there (the STRAVA_CLIENT_SECRET).
There was a problem hiding this comment.
Yeah sure. I’ll create the StravaClientSecret file in commonMain. Also, Do you have any recommendations or resources on how to manage the security of an app? I'd love to learn more
There was a problem hiding this comment.
I haven't had to deal too much with this before, but it is a really big topic that would be very valuable for us to learn more about.
One problem with what I am doing is that it is possible for people to "reverse-engineer" applications. That means people that have applications on their phone can find ways to get the code of an application. If our client secret is in the code, like it is here (even though not on GitHub) that is a problem :X
One solution, I think, is to create our own server and then use the server to give "clients" the client secret, rather than it being a part of the code.
One other problem with putting the client secret in code is that, if this ever is "compromised" (stolen, found-out) then we need to basically tell Strava that our client secret got compromised and we need a new one. That would make all the old versions of the application broken forever because the hard-coded client secret is now not updated. Does that make sense?
| class StravaRepository( | ||
| private val api: StravaApi |
There was a problem hiding this comment.
The reason we use "repository" is part of clean architecture. The idea is that a repository is a middle-layer between the data source StravaApi and the application. It's kind of redundant with respect to the UseCase we have.
| return client.post(urlString = StravaEndpoints.OAUTH_TOKEN) { | ||
| parameter(key = "client_id", value = clientId) | ||
| parameter(key = "client_secret", value = clientSecret) | ||
| parameter(key = "code", value = code) | ||
| parameter(key = "grant_type", value = StravaGrantTypes.AUTH_CODE) | ||
| }.body() | ||
| } |
There was a problem hiding this comment.
This is Ktor. It is a networking library.
Another library is Retrofit, it is very popular but only works with Android. As this is a Kotlin Multiplatform project, we use Ktor instead.
There was a problem hiding this comment.
Here is what Retrofit looks like in my other application that uses Strava https://github.com/Tyler-Lopez/StravaActivityArt/blob/main/app/src/main/java/com/activityartapp/data/remote/AthleteApi.kt
You can see it is "annotation-based" like @GET("url")
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class StravaAuthenticationDto( |
There was a problem hiding this comment.
Dto stands for data transfer object. The idea is that this lives in the data layer and just helps us work with the "response" that we get from network requests.
| @OptIn(ExperimentalTime::class) | ||
| suspend operator fun invoke() { | ||
| val accessToken = repository.getAccessToken(code = stravaAccessCode) | ||
|
|
||
| repository.createActivity( | ||
| accessToken = accessToken.accessToken, | ||
| name = "Created from HIIT App", | ||
| type = "HighIntensityIntervalTraining", | ||
| startDateLocal = TimeZone.currentSystemDefault() | ||
| .let { tz -> | ||
| Clock.System.now() | ||
| .minus(1.hours) | ||
| .toLocalDateTime(tz) | ||
| .toString() | ||
| }, | ||
| elapsedTime = 60, | ||
| ) | ||
| } |
There was a problem hiding this comment.
In Android Studio, you can see every network request that your application makes: the request and the response in something called the Network Inspector.
Here, I can see both of my network requests in this CreateStravaActivityUseCase: first I get the access token, and then I make the Activity on Strava.
I can also see the "response" code. It is 200 and 201 :) Both of which mean success, 201 means something new was made, which is correct: we made an activity.
| object HttpClientFactory { | ||
|
|
||
| fun create(): HttpClient { | ||
| return HttpClient { | ||
| install(ContentNegotiation) { | ||
| json( | ||
| Json { | ||
| ignoreUnknownKeys = true | ||
| } | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
This is just boiler plate, and I should probably re-write this to be better. I would ignore this in terms of your learning.
| if (viewModel.showCreateActivityButton) { | ||
| Button( | ||
| onClick = { | ||
| viewModel.onEvent(event = HomeViewEvent.ClickedCreateStravaActivity) | ||
| } | ||
| ) { | ||
| Text(text = "Create Strava Activity") | ||
| } | ||
| } |
There was a problem hiding this comment.
Just temporary that this is here. When you click the Button it makes a Strava Activity that happened an hour ago and lasted 60 seconds. We'll move this later.
| private val httpClient by lazy { | ||
| HttpClientFactory.create() | ||
| } | ||
|
|
||
| private val stravaApi: StravaApi by lazy { | ||
| StravaApi(httpClient) | ||
| } | ||
|
|
||
| private val stravaRepository by lazy { | ||
| StravaRepository(stravaApi) | ||
| } | ||
| private val createStravaActivityUseCase by lazy { | ||
| CreateStravaActivityUseCase( | ||
| stravaAccessCode = stravaAccessCode ?: "", | ||
| repository = stravaRepository, | ||
| ) | ||
| } |
There was a problem hiding this comment.
This is all bad practice - how I get createStravaActivityUseCase in the ViewModel, and in the future we will use dependency injection here. I'm just not quite sure how to implement that yet. You'll see how it is much better in the future :)
| xcuserdata | ||
| !src/**/build/ | ||
| local.properties | ||
| StravaClientSecret.kt |
There was a problem hiding this comment.
Here I added StravaClientSecret.kt to the .gitignore so we never commit that file so others can see it 😬
| import io.ktor.http.HttpHeaders | ||
| import io.ktor.http.Parameters | ||
|
|
||
| class StravaApi(private val client: HttpClient) { |
There was a problem hiding this comment.
Check out this page: https://developers.strava.com/docs/reference/#api-Activities-createActivity
:)
That lists everything we can do with the Strava API. All I added here was just something to get the access token and also to "Create an Activity".

Summary
This commit adds the first HTTP networking end-point to the application: the Strava API.
We take the access code we got from the last PR, and now use that to get a proper access token. With the access token we create a Strava Activity.
All of this is temporary for now. This was added to the main screen. We should add this to some screen that you go to after completing a workout instead in the future.
Demo
Testing